popovermenu: Allow custom items
authorMatthias Clasen <mclasen@redhat.com>
Tue, 20 Oct 2020 18:26:45 +0000 (14:26 -0400)
committerMatthias Clasen <mclasen@redhat.com>
Tue, 27 Oct 2020 02:41:33 +0000 (22:41 -0400)
Add a way to add children at certain places in
the generated menu for both GtkPopoverMenu and
GtkPopoverMenuBar.

New apis:
gtk_popover_menu_add_child
gtk_popover_menu_remove_child
gtk_popover_menu_bar_add_child
gtk_popover_menu_bar_remove_child

Fixes: #3260
docs/reference/gtk/gtk4-sections.txt
gtk/gtkmenusectionbox.c
gtk/gtkmenusectionboxprivate.h
gtk/gtkmenutrackeritem.c
gtk/gtkmenutrackeritemprivate.h
gtk/gtkpopovermenu.c
gtk/gtkpopovermenu.h
gtk/gtkpopovermenubar.c
gtk/gtkpopovermenubar.h

index a0e123947b912466c0be6caeff3777c6aa5a0423..b6f7cf2d25baeda9c4d2d836f81c9c5adacae7df 100644 (file)
@@ -6177,6 +6177,8 @@ gtk_popover_menu_new_from_model
 gtk_popover_menu_new_from_model_full
 gtk_popover_menu_set_menu_model
 gtk_popover_menu_get_menu_model
+gtk_popover_menu_add_child
+gtk_popover_menu_remove_child
 
 <SUBSECTION Standard>
 GTK_TYPE_POPOVER_MENU
@@ -6195,6 +6197,8 @@ GtkPopoverMenuBar
 gtk_popover_menu_bar_new_from_model
 gtk_popover_menu_bar_set_menu_model
 gtk_popover_menu_bar_get_menu_model
+gtk_popover_menu_bar_add_child
+gtk_popover_menu_bar_remove_child
 
 <SUBSECTION Standard>
 GTK_TYPE_POPOVER_MENU_BAR
index 08f5ca128816a5f066bbd3b2975ed756bbe71ebf..9f988d98f52ec035d74c0e8506fc355f728108c9 100644 (file)
@@ -32,6 +32,8 @@
 #include "gtkpopovermenuprivate.h"
 #include "gtkorientable.h"
 #include "gtkbuiltiniconprivate.h"
+#include "gtkgizmoprivate.h"
+#include "gtkbinlayout.h"
 
 typedef GtkBoxClass GtkMenuSectionBoxClass;
 
@@ -50,6 +52,7 @@ struct _GtkMenuSectionBox
   int                  depth;
   GtkPopoverMenuFlags  flags;
   GtkSizeGroup        *indicators;
+  GHashTable          *custom_slots;
 };
 
 typedef struct
@@ -341,6 +344,22 @@ gtk_menu_section_box_insert_func (GtkMenuTrackerItem *item,
           g_free (name);
         }
     }
+  else if (gtk_menu_tracker_item_get_custom (item))
+    {
+      const char *id = gtk_menu_tracker_item_get_custom (item);
+
+      widget = gtk_gizmo_new ("widget", NULL, NULL, NULL, NULL, NULL, NULL);
+      gtk_widget_set_layout_manager (widget, gtk_bin_layout_new ());
+
+      if (g_hash_table_lookup (box->custom_slots, id))
+        g_warning ("Duplicate custom ID: %s", id);
+      else
+        {
+          char *slot_id = g_strdup (id);
+          g_object_set_data_full (G_OBJECT (widget), "slot-id", slot_id, g_free);
+          g_hash_table_insert (box->custom_slots, slot_id, widget);
+        }
+    }
   else
     {
       widget = g_object_new (GTK_TYPE_MODEL_BUTTON,
@@ -458,6 +477,7 @@ gtk_menu_section_box_dispose (GObject *object)
     }
 
   g_clear_object (&box->indicators);
+  g_clear_pointer (&box->custom_slots, g_hash_table_unref);
 
   G_OBJECT_CLASS (gtk_menu_section_box_parent_class)->dispose (object);
 }
@@ -499,8 +519,9 @@ gtk_menu_section_box_new_toplevel (GtkPopoverMenu      *popover,
 {
   GtkMenuSectionBox *box;
 
-  box = g_object_new (GTK_TYPE_MENU_SECTION_BOX,  NULL);
+  box = g_object_new (GTK_TYPE_MENU_SECTION_BOX, NULL);
   box->indicators = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+  box->custom_slots = g_hash_table_new (g_str_hash, g_str_equal);
   box->flags = flags;
 
   gtk_popover_menu_add_submenu (popover, GTK_WIDGET (box), "main");
@@ -524,6 +545,7 @@ gtk_menu_section_box_new_submenu (GtkMenuTrackerItem *item,
 
   box = g_object_new (GTK_TYPE_MENU_SECTION_BOX, NULL);
   box->indicators = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+  box->custom_slots = g_hash_table_ref (toplevel->custom_slots);
   box->flags = toplevel->flags;
 
   button = g_object_new (GTK_TYPE_MODEL_BUTTON,
@@ -562,6 +584,7 @@ gtk_menu_section_box_new_section (GtkMenuTrackerItem *item,
 
   box = g_object_new (GTK_TYPE_MENU_SECTION_BOX, NULL);
   box->indicators = g_object_ref (parent->indicators);
+  box->custom_slots = g_hash_table_ref (parent->toplevel->custom_slots);
   box->toplevel = parent->toplevel;
   box->depth = parent->depth + 1;
   box->flags = parent->flags;
@@ -661,3 +684,54 @@ gtk_menu_section_box_new_section (GtkMenuTrackerItem *item,
 
   return GTK_WIDGET (box);
 }
+
+gboolean
+gtk_menu_section_box_add_custom (GtkPopoverMenu *popover,
+                                 GtkWidget      *child,
+                                 const char     *id)
+{
+  GtkWidget *stack;
+  GtkMenuSectionBox *box;
+  GtkWidget *slot;
+
+  stack = gtk_popover_get_child (GTK_POPOVER (popover));
+  box = GTK_MENU_SECTION_BOX (gtk_stack_get_child_by_name (GTK_STACK (stack), "main"));
+
+  slot = (GtkWidget *)g_hash_table_lookup (box->custom_slots, id);
+
+  if (slot == NULL)
+    return FALSE;
+
+  if (gtk_widget_get_first_child (slot))
+    return FALSE;
+
+  gtk_widget_insert_before (child, slot, NULL);
+  return TRUE;
+}
+
+gboolean
+gtk_menu_section_box_remove_custom (GtkPopoverMenu *popover,
+                                    GtkWidget      *child)
+{
+  GtkWidget *stack;
+  GtkMenuSectionBox *box;
+  GtkWidget *parent;
+  const char *id;
+  GtkWidget *slot;
+
+  stack = gtk_popover_get_child (GTK_POPOVER (popover));
+  box = GTK_MENU_SECTION_BOX (gtk_stack_get_child_by_name (GTK_STACK (stack), "main"));
+  parent = gtk_widget_get_parent (child);
+
+  id = (const char *) g_object_get_data (G_OBJECT (parent), "slot-id");
+  g_return_val_if_fail (id != NULL, FALSE);
+
+  slot = (GtkWidget *)g_hash_table_lookup (box->custom_slots, id);
+
+  if (slot != parent)
+    return FALSE;
+
+  gtk_widget_unparent (child);
+
+  return TRUE;
+}
index 05d6bd294e5bd459d263144249fd594b354f5c52..232d62ee0c629b4134fc26a3e57cf5bbff8fbd31 100644 (file)
@@ -45,6 +45,13 @@ void                    gtk_menu_section_box_new_toplevel               (GtkPopo
                                                                          GMenuModel          *model,
                                                                          GtkPopoverMenuFlags  flags);
 
+gboolean                gtk_menu_section_box_add_custom                 (GtkPopoverMenu *popover,
+                                                                         GtkWidget      *child,
+                                                                         const char     *id);
+
+gboolean                gtk_menu_section_box_remove_custom              (GtkPopoverMenu *popover,
+                                                                         GtkWidget      *child);
+
 G_END_DECLS
 
 #endif /* __GTK_MENU_SECTION_BOX_PRIVATE_H__ */
index f88009c7d309645538b14aa14549288fddec8081..93536df960ab480aa2248b5d219bfe1545ab153c 100644 (file)
@@ -715,6 +715,16 @@ gtk_menu_tracker_item_get_special (GtkMenuTrackerItem *self)
   return special;
 }
 
+const char *
+gtk_menu_tracker_item_get_custom (GtkMenuTrackerItem *self)
+{
+  const char *custom = NULL;
+
+  g_menu_item_get_attribute (self->item, "custom", "&s", &custom);
+
+  return custom;
+}
+
 const char *
 gtk_menu_tracker_item_get_display_hint (GtkMenuTrackerItem *self)
 {
index a3c9164053f2899a6687a29db5343f7d59e2c1a7..6ebbc3764c1b50263e4048bbe6b501fe7818a0ef 100644 (file)
@@ -51,6 +51,8 @@ GtkMenuTrackerItem *   _gtk_menu_tracker_item_new                       (GtkActi
 
 const char *           gtk_menu_tracker_item_get_special               (GtkMenuTrackerItem *self);
 
+const char *           gtk_menu_tracker_item_get_custom                (GtkMenuTrackerItem *self);
+
 const char *           gtk_menu_tracker_item_get_display_hint          (GtkMenuTrackerItem *self);
 
 const char *           gtk_menu_tracker_item_get_text_direction        (GtkMenuTrackerItem *self);
index dab66355301658979e2fb56f93ba3c97ef52b220..4c33b0011dce24c4cd58dc0eb8dd983b41ac3420 100644 (file)
@@ -98,6 +98,8 @@
  * - "hidden-when": a string used to determine when the item will be hidden.
  *      Possible values include "action-disabled", "action-missing", "macos-menubar".
  *      This is mainly useful for exported menus, see gtk_application_set_menubar().
+ * - "custom": a string used to match against the ID of a custom child added
+ *      with gtk_popover_menu_add_child() or gtk_popover_menu_bar_add_child().
  *
  * The following attributes are used when constructing sections:
  * - "label": a user-visible string to use as section heading
@@ -733,3 +735,50 @@ gtk_popover_menu_get_menu_model (GtkPopoverMenu *popover)
 
   return popover->model;
 }
+
+/**
+ * gtk_popover_menu_add_child:
+ * @popover: a #GtkPopoverMenu
+ * @child: the #GtkWidget to add
+ * @id: the ID to insert @child at
+ *
+ * Adds a custom widget to a generated menu.
+ *
+ * For this to work, the menu model of @popover must have an
+ * item with a `custom` attribute that matches @id.
+ *
+ * Returns: %TRUE if @id was found and the widget added
+ */
+gboolean
+gtk_popover_menu_add_child (GtkPopoverMenu *popover,
+                            GtkWidget      *child,
+                            const char     *id)
+{
+
+  g_return_val_if_fail (GTK_IS_POPOVER_MENU (popover), FALSE);
+  g_return_val_if_fail (GTK_IS_WIDGET (child), FALSE);
+  g_return_val_if_fail (id != NULL, FALSE);
+
+  return gtk_menu_section_box_add_custom (popover, child, id);
+}
+
+/**
+ * gtk_popover_menu_remove_child:
+ * @popover: a #GtkPopoverMenu
+ * @child: the #GtkWidget to remove
+ *
+ * Removes a widget that has previously been added with
+ * gtk_popover_menu_add_child().
+ *
+ * Returns: %TRUE if the widget was removed
+ */
+gboolean
+gtk_popover_menu_remove_child (GtkPopoverMenu *popover,
+                               GtkWidget      *child)
+{
+
+  g_return_val_if_fail (GTK_IS_POPOVER_MENU (popover), FALSE);
+  g_return_val_if_fail (GTK_IS_WIDGET (child), FALSE);
+
+  return gtk_menu_section_box_remove_custom (popover, child);
+}
index f9f8457b27be6427b4e00c6a00fa95b050751652..2973102be15e742e769eab938397bcc7a9f4f4ed 100644 (file)
@@ -61,6 +61,14 @@ void        gtk_popover_menu_set_menu_model (GtkPopoverMenu *popover,
 GDK_AVAILABLE_IN_ALL
 GMenuModel *gtk_popover_menu_get_menu_model (GtkPopoverMenu *popover);
 
+GDK_AVAILABLE_IN_ALL
+gboolean    gtk_popover_menu_add_child (GtkPopoverMenu *popover,
+                                        GtkWidget      *child,
+                                        const char     *id);
+
+GDK_AVAILABLE_IN_ALL
+gboolean    gtk_popover_menu_remove_child (GtkPopoverMenu *popover,
+                                           GtkWidget      *child);
 
 G_END_DECLS
 
index 1401c998ebdff8ac9398b757bf3c3e9ce728c97b..0d5658c2249bec2000477128fcf22e59725c1600 100644 (file)
@@ -733,3 +733,73 @@ gtk_popover_menu_bar_select_first (GtkPopoverMenuBar *bar)
   item = GTK_POPOVER_MENU_BAR_ITEM (gtk_widget_get_first_child (GTK_WIDGET (bar)));
   set_active_item (bar, item, TRUE);
 }
+
+/**
+ * gtk_popover_menu_bar_add_child:
+ * @bar: a #GtkPopoverMenuBar
+ * @child: the #GtkWidget to add
+ * @id: the ID to insert @child at
+ *
+ * Adds a custom widget to a generated menubar.
+ *
+ * For this to work, the menu model of @bar must have an
+ * item with a `custom` attribute that matches @id.
+ *
+ * Returns: %TRUE if @id was found and the widget added
+ */
+gboolean
+gtk_popover_menu_bar_add_child (GtkPopoverMenuBar *bar,
+                                GtkWidget         *child,
+                                const char        *id)
+{
+  GtkWidget *item;
+
+  g_return_val_if_fail (GTK_IS_POPOVER_MENU_BAR (bar), FALSE);
+  g_return_val_if_fail (GTK_IS_WIDGET (child), FALSE);
+  g_return_val_if_fail (id != NULL, FALSE);
+
+  for (item = gtk_widget_get_first_child (GTK_WIDGET (bar));
+       item;
+       item = gtk_widget_get_next_sibling (item))
+    {
+      GtkPopover *popover = GTK_POPOVER_MENU_BAR_ITEM (item)->popover;
+
+      if (gtk_popover_menu_add_child (GTK_POPOVER_MENU (popover), child, id))
+        return TRUE;
+    }
+
+  return FALSE;
+}
+
+/**
+ * gtk_popover_menu_bar_remove_child:
+ * @bar: a #GtkPopoverMenuBar
+ * @child: the #GtkWidget to remove
+ *
+ * Removes a widget that has previously been added with
+ * gtk_popover_menu_bar_add_child().
+ *
+ * Returns: %TRUE if the widget was removed
+ */
+gboolean
+gtk_popover_menu_bar_remove_child (GtkPopoverMenuBar *bar,
+                                   GtkWidget         *child)
+{
+  GtkWidget *item;
+
+  g_return_val_if_fail (GTK_IS_POPOVER_MENU_BAR (bar), FALSE);
+  g_return_val_if_fail (GTK_IS_WIDGET (child), FALSE);
+
+  for (item = gtk_widget_get_first_child (GTK_WIDGET (bar));
+       item;
+       item = gtk_widget_get_next_sibling (item))
+    {
+      GtkPopover *popover = GTK_POPOVER_MENU_BAR_ITEM (item)->popover;
+
+      if (gtk_popover_menu_remove_child (GTK_POPOVER_MENU (popover), child))
+        return TRUE;
+    }
+
+  return FALSE;
+}
+
index 7fa12531280249dafcf8357c31e7c3f3485f3aae..08126f9d63b45cb96912dba71c55731fd9c2c8d3 100644 (file)
@@ -47,6 +47,14 @@ void         gtk_popover_menu_bar_set_menu_model (GtkPopoverMenuBar *bar,
 GDK_AVAILABLE_IN_ALL
 GMenuModel * gtk_popover_menu_bar_get_menu_model (GtkPopoverMenuBar *bar);
 
+GDK_AVAILABLE_IN_ALL
+gboolean     gtk_popover_menu_bar_add_child      (GtkPopoverMenuBar *bar,
+                                                  GtkWidget         *child,
+                                                  const char        *id);
+
+GDK_AVAILABLE_IN_ALL
+gboolean     gtk_popover_menu_bar_remove_child   (GtkPopoverMenuBar *bar,
+                                                  GtkWidget         *child);
 G_END_DECLS